package textUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;


/**
 *  The Class to Compare Texts 
 */
public class Compare 
{
	public Compare()
	{}
	
	
	/**
	 * A static method to convert String array obtained from 'split[RegEx]' method to 'HashMap<Integer, String>'
	 * @param array is the String array obtained from 'split[RegEx]'
	 * @return returns the 'HashMap<Integer, String>' version of the String array passed as parameter.
	 */
	public static HashMap<Integer,String> arrayToMap(String[] array)
	{
		HashMap<Integer,String> map = new HashMap<Integer,String>();
		int arrayLength = array.length;
		
		for(int i=0; i<arrayLength; i++)
			map.put(i, array[i]);
		
		return map;
	}
	
	
	/**
	 * A method to maintain the sequence of the keys in a HashMap, possibly after removal of an entry. 
	 * @param toSerialize is the HashMap to have the Integer keys re-sequenced. 
	 * @param startFrom is the index that has been deleted.
	 * @return returns re-sequenced HashMap.
	 */
	private static HashMap<Integer,String> reSequenceKeysOf(HashMap<Integer,String> toSerialize, int startFrom)
	{
		while( toSerialize.containsKey(startFrom+1) )
			toSerialize.put(startFrom, toSerialize.get(++startFrom));
		
		toSerialize.remove(startFrom);
		return toSerialize;
	}
		
	
	/**
	 * Private method to reduce code-repetition, the case of two HashMaps having the same number of lines to compare.   
	 * @param stringMap_1 The first HashMap of Strings.
	 * @param stringMap_2 The second HashMap of Strings.
	 * @param startFrom The starting index value to put as key in the newly constructed HashMaps, 
	 * so that they can be consistently combined into the final String. 
	 * (Typically it's the Integer value of the index value where the number 
	 * of the sentences in the HashMaps turned the same.)  
	 * @return returns an ArrayList Collection of matched Strings, and two sets of unmatched Strings in HashMaps.
	 */
	private static Collection<HashMap<Integer,String>> equalCase(
			HashMap<Integer,String> stringMap_1, HashMap<Integer,String> stringMap_2, int startFrom)
	{
		int size = stringMap_2.size();
		HashMap<Integer,String> matchedLines = new HashMap<Integer, String>();
		HashMap<Integer,String> unMatchedLines_1 = new HashMap<Integer, String>();
		HashMap<Integer,String> unMatchedLines_2 = new HashMap<Integer, String>();
		Collection<HashMap<Integer,String>> equalCaseLines = new ArrayList<HashMap<Integer,String>>();
		
		for(int index = startFrom, run=startFrom; index<size && run<size; run++)
		{
			if(stringMap_1.get(index).equalsIgnoreCase(stringMap_2.get(index)))					
				{
					matchedLines.put(run, stringMap_2.get(index));
					index++;
				}
			else
			{
				
				unMatchedLines_1.put(run, stringMap_1.get(index));
				unMatchedLines_2.put(run, stringMap_2.get(index));
				
				stringMap_2.remove(index);
				stringMap_2 = reSequenceKeysOf(stringMap_2, index);
				
				stringMap_1.remove(index);
				stringMap_1 = reSequenceKeysOf(stringMap_1, index);
				
			}
		}
		
		equalCaseLines.add(matchedLines);
		equalCaseLines.add(unMatchedLines_1);
		equalCaseLines.add(unMatchedLines_2);
		
		return equalCaseLines;
		
	}
		
	/**
	 * A method for merging two HashMaps.
	 * @param map_1 HashMap one.
	 * @param map_2 HashMap two.
	 * @return returns merged HashMap of map_1 and map_2
	 */
	public static HashMap<Integer, String> mergeMaps(
			HashMap<Integer, String> map_1,
			HashMap<Integer, String> map_2) {
		
		HashMap<Integer, String> toReturn = new HashMap<Integer, String>();
		toReturn.putAll(map_1);
		toReturn.putAll(map_2);
		
		return toReturn;
	}
		
	
	/**
	 * A method non-destructively merge two text Strings.
	 * @param string1 First String to merge.
	 * @param string2 Second String to merge.
	 * @return Returns merged String.
	 */
	public static String merge(String string1, String string2)
	{
		string1.replaceAll(">>>conflicted>>>|<<<conflicted<<<", ".");
		String [] stringToCompare_1 = string1.split("(?<=[?!\\.]+)");
		String [] stringToCompare_2 = string2.split("(?<=[?!\\.]+)");
		
		int lowerLimit;
		int upperLimit;
		HashMap<Integer,String> smallerStringMap;
		HashMap<Integer,String> largerStringMap;
		HashMap<Integer,String> matchedLines = new HashMap<Integer, String>();
		HashMap<Integer,String> unMatchedLines = new HashMap<Integer, String>();
		String mergedStringToReturn = "";
		
		if(stringToCompare_1.length != stringToCompare_2.length)
		{
			if(stringToCompare_1.length > stringToCompare_2.length)
			{
				lowerLimit = stringToCompare_2.length;
				upperLimit = stringToCompare_1.length;
				smallerStringMap = arrayToMap(stringToCompare_2);
				largerStringMap = arrayToMap(stringToCompare_1); // case 1
			}
			else
			{
				lowerLimit = stringToCompare_1.length;
				upperLimit = stringToCompare_2.length;
				smallerStringMap = arrayToMap(stringToCompare_1);
				largerStringMap = arrayToMap(stringToCompare_2);// case 2
			}
			
			
			for(int index = 0, run=0; index<lowerLimit && run<upperLimit; run++)
			{	
				if(largerStringMap.get(index).equalsIgnoreCase(smallerStringMap.get(index)))
				{
					matchedLines.put(run, largerStringMap.get(index));
					index++;
				}
				else
				{
					unMatchedLines.put(run, largerStringMap.get(index));
					largerStringMap.remove(index);
					largerStringMap = reSequenceKeysOf(largerStringMap, index);
					
					//branched into equal line numbers case
					int limit = smallerStringMap.size();
					if(largerStringMap.size()==smallerStringMap.size())
						{
							ArrayList<HashMap<Integer,String>> equalCaseMaps = 
									(ArrayList<HashMap<Integer, String>>) equalCase(largerStringMap, smallerStringMap,run);
							
							
							matchedLines = mergeMaps(matchedLines, equalCaseMaps.get(0));
							HashMap<Integer, String> unMatchedLines_1 = equalCaseMaps.get(1);
							HashMap<Integer, String> unMatchedLines_2 = equalCaseMaps.get(2);
							
							
							
							int mismatchCount = unMatchedLines_1.size();
							for(int i=0; i< limit+mismatchCount; i++)
							{
								if(matchedLines.containsKey(i))
									mergedStringToReturn += matchedLines.get(i);
								if(unMatchedLines.containsKey(i))
									mergedStringToReturn += ">>>conflicted>>>"+unMatchedLines.get(i);
								if(unMatchedLines_1.containsKey(i) && unMatchedLines_2.containsKey(i))
									mergedStringToReturn += unMatchedLines_1.get(i) + unMatchedLines_2.get(i) +"<<<conflicted<<<";
							}
							return mergedStringToReturn;
							
						}
				}
			}
			
			
			//reconstruction of the final merged string, unequal number of lines case. 
			int maxLength =Math.max(stringToCompare_1.length, stringToCompare_2.length);
			for(int i=0; i<maxLength; i++)
			{
				mergedStringToReturn += matchedLines.containsKey(i) ? matchedLines.get(i) : 
									 		(unMatchedLines.containsKey(i)? ">>>conflicted>>>"+unMatchedLines.get(i)+"<<<conflicted<<<" : "") ; 
			}
			return mergedStringToReturn;
			
		}else //equal number of lines case.
		{
			int limit = stringToCompare_1.length;
			
			HashMap<Integer,String> stringMap_1 = arrayToMap(stringToCompare_1);
			HashMap<Integer,String> stringMap_2 = arrayToMap(stringToCompare_2);
			
			ArrayList<HashMap<Integer,String>> equalCaseMaps = 
					(ArrayList<HashMap<Integer, String>>) equalCase(stringMap_1, stringMap_2,0);
			
			matchedLines = mergeMaps(matchedLines, equalCaseMaps.get(0));
			HashMap<Integer, String> unMatchedLines_1 = equalCaseMaps.get(1);
			HashMap<Integer, String> unMatchedLines_2 = equalCaseMaps.get(2);
			int mismatchCount = unMatchedLines_1.size();
			
			//reconstruction of the final merged string, equal number of lines case.
			for(int i=0; i< limit+mismatchCount; i++)
			{
				mergedStringToReturn += matchedLines.containsKey(i) ? matchedLines.get(i) : 
											(unMatchedLines_1.containsKey(i) ? ">>>conflicted>>>"+unMatchedLines_1.get(i) 
											+(unMatchedLines_2.containsKey(i) ? unMatchedLines_2.get(i) +"<<<conflicted<<<": "")
																											: "");
			}
			return mergedStringToReturn;
		}
		
	}


    /**
     * update (merge) the texts
     * @param root the root text
     * @param version_1 different version
     * @param version_2 another version
     * @return return the merged versions
     */
	public static String update(String root, String version_1, String version_2)
	{
		
		String [] rootArray = root.split("(?<=[ ]+)");
		String [] v_1_Array = version_1.split("(?<=[ ]+)");
		String [] v_2_Array = version_1.split("(?<=[ ]+)");
		String finalString="";
		
		if(rootArray.length == v_1_Array.length && v_1_Array.length == v_2_Array.length)
		{
			int length = rootArray.length;
			
			for(int i=0; i<length; i++)
			{
				if(  !v_1_Array[i].equalsIgnoreCase(v_2_Array[i])  )
					if(   v_1_Array[i].equalsIgnoreCase(rootArray[i])  )
						v_2_Array[i] = rootArray[i];
					
				
				finalString += v_2_Array[i];
			}
			return finalString;
		}
		else
			return version_2;
	}

}


	
